package com.ziven.common.widget.index;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.ziven.common.widget.R;
import com.ziven.common.widget.WidgetUtils;
import com.ziven.easygo.util.BiCarry;
import com.ziven.easygo.util.Carry;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Ziven
 */
public class IndexLayout extends View implements IndexInterface {

    public static final int NOT_INIT = -1;
    public static final String NOT_INIT_STR = "NOT_INIT_STR";

    private IndexGetData indexGetData;
    private final List<IndexCallback> indexCallbacks;

    private IndexNameBlock[] indexNameBlocks;
    private final IndexNameBlock selIndexNameBlock;

    private final Paint indexNamePaint;
    private final Paint indexNameSelPaint;

    private final int indexNameHeight;
    private final int indexNameAscent;
    private final int showPaddingStart;
    private final int showPaddingTop;
    private final int showPaddingBottom;
    private final int indexNameMaxGap;
    private int indexNameGap;
    private int viewHeight = NOT_INIT;
    private int viewWidth = NOT_INIT;
    private boolean isTouching = false;

    public IndexLayout(Context context) {
        this(context, null);
    }

    public IndexLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public IndexLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public IndexLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        indexCallbacks = new ArrayList<>();
        indexNameBlocks = new IndexNameBlock[0];
        selIndexNameBlock = new IndexNameBlock();
        selIndexNameBlock.update(NOT_INIT, NOT_INIT_STR);

        indexNamePaint = new Paint();
        indexNameSelPaint = new Paint();

        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IndexLayout, defStyleAttr, defStyleRes);
        showPaddingStart = typedArray.getDimensionPixelSize(R.styleable.IndexLayout_show_padding_start, 0);
        showPaddingTop = typedArray.getDimensionPixelSize(R.styleable.IndexLayout_show_padding_top, 0);
        showPaddingBottom = typedArray.getDimensionPixelSize(R.styleable.IndexLayout_show_padding_bottom, 0);
        indexNameMaxGap = typedArray.getDimensionPixelSize(R.styleable.IndexLayout_index_name_max_gap, 0);

        int indexNameColor = typedArray.getColor(R.styleable.IndexLayout_index_name_color, Color.BLACK);
        int indexNameSelColor = typedArray.getColor(R.styleable.IndexLayout_index_name_sel_color, Color.WHITE);
        int indexNameSize = typedArray.getDimensionPixelSize(R.styleable.IndexLayout_index_name_size, WidgetUtils.dimenInt("12sp"));
        int indexNameTextStyle = typedArray.getInt(R.styleable.IndexLayout_index_name_text_style, Typeface.NORMAL);
        String indexNameFontFamily = typedArray.getString(R.styleable.IndexLayout_index_name_font_family);
        Typeface typeface = createTypeface(indexNameTextStyle, indexNameFontFamily);

        indexNamePaint.setColor(indexNameColor);
        indexNamePaint.setTextSize(indexNameSize);
        indexNamePaint.setTypeface(typeface);

        indexNameSelPaint.setColor(indexNameSelColor);
        indexNameSelPaint.setTextSize(indexNameSize);
        indexNameSelPaint.setTypeface(typeface);

        Paint.FontMetrics fm = indexNamePaint.getFontMetrics();
        indexNameHeight = (int) (fm.descent - fm.ascent + 0.5);
        indexNameAscent = (int) (0.5 - fm.ascent);

        typedArray.recycle();
    }

    private Typeface createTypeface(int textStyle, String fontFamily) {
        if(!TextUtils.isEmpty(fontFamily)) {
            return Typeface.create(fontFamily, textStyle);
        }
        return Typeface.defaultFromStyle(textStyle);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isTouching = true;
                touchingDispatch(true, (int) event.getX(), (int) event.getY());
                break;
            case MotionEvent.ACTION_MOVE:
                touchingDispatch(null, (int) event.getX(), (int) event.getY());
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                isTouching = false;
                touchingDispatch(false, (int) event.getX(), (int) event.getY());
                break;
            default:
                break;
        }
        return true;
    }

    private void touchingDispatch(Boolean touch, int x, int y) {
        if(indexNameBlocks.length == 0) {
            return;
        }
        if(touch != null && touch) {
            selIndexNameBlock.update(NOT_INIT, NOT_INIT_STR);
            updateIndexName(selIndexNameBlock.index, selIndexNameBlock.indexName);
            IndexNameBlock first = indexNameBlocks[0];
            IndexNameBlock last = indexNameBlocks[indexNameBlocks.length - 1];
            indexCallbacks.forEach(callback -> {
                callback.startUpdateIndexName(
                        first.touchRect.top + indexNameGap,
                        last.touchRect.bottom - indexNameGap
                );
//                if(selIndexNameBlock.index != NOT_INIT) {
//                    callback.updateIndexName(
//                            selIndexNameBlock.index,
//                            selIndexNameBlock.indexName,
//                            selIndexNameBlock.drawX,
//                            selIndexNameBlock.centerY
//                    );
//                }
            });
        }
        forEachIndexNameBlocks(block -> {
            if(block.inTouchRect(x, y) && block.index != selIndexNameBlock.index) {
                block.copy(selIndexNameBlock);
                updateIndexName(selIndexNameBlock.index, selIndexNameBlock.indexName);
                if(selIndexNameBlock.index != NOT_INIT) {
                    indexCallbacks.forEach(callback -> callback.updateIndexName(
                            selIndexNameBlock.index,
                            selIndexNameBlock.indexName,
                            selIndexNameBlock.drawX,
                            selIndexNameBlock.centerY
                    ));
                }
            }
        });
        if(touch != null && !touch) {
            updateIndexName(selIndexNameBlock.index, selIndexNameBlock.indexName);
            indexCallbacks.forEach(callback -> {
                if(selIndexNameBlock.index != NOT_INIT) {
                    callback.updateIndexName(
                            selIndexNameBlock.index,
                            selIndexNameBlock.indexName,
                            selIndexNameBlock.drawX,
                            selIndexNameBlock.centerY
                    );
                }
                callback.endUpdateIndexName();
            });
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(updateViewHeightWidth()) {
            updateIndexNameGap();
            forEachIndexNameBlocks(block -> block.update(
                    viewWidth,
                    viewHeight,
                    indexNameHeight,
                    indexNameAscent,
                    indexNameGap,
                    showPaddingStart,
                    showPaddingTop,
                    showPaddingBottom
            ));
        }
    }

    private boolean updateViewHeightWidth() {
        int height = getMeasuredHeight(), width = getMeasuredWidth();
        if(viewHeight == height && viewWidth == width) {
            return false;
        }
        viewHeight = height;
        viewWidth = width;
        return true;
    }

    private void updateIndexNameGap() {
        int count = indexNameBlocks.length;
        int showMaxHeight = viewHeight - showPaddingTop - showPaddingBottom;
        int showRealHeight = (indexNameHeight + indexNameMaxGap) * count;
        indexNameGap = showRealHeight > showMaxHeight
                ? Math.max(0, (showMaxHeight - indexNameHeight * count) / count)
                : indexNameMaxGap;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        forEachIndexNameBlocks(block -> block.draw(canvas,
                /*!isTouching &&*/selIndexNameBlock.index == block.index
                        ? indexNameSelPaint
                        : indexNamePaint));
    }

    @Override
    public void setIndexGetData(@NonNull IndexGetData indexGetData) {
        this.indexGetData = indexGetData;
    }

    @Override
    public void setIndexCallback(@NonNull IndexCallback indexCallback) {
        indexCallbacks.add(indexCallback);
    }

    @Override
    public void removeIndexCallback(@NonNull IndexCallback indexCallback) {
        indexCallbacks.remove(indexCallback);
    }

    @Override
    public void notifyDataUpdated() {
        updateIndexNameBlock();
        updateIndexName(NOT_INIT, NOT_INIT_STR);
    }

    private void updateIndexNameBlock() {
        int count;
        String[] indexNames;
        if((count = (indexNames = indexGetData.getIndexNames()).length) == 0) {
            indexNameBlocks = new IndexNameBlock[count];
            return;
        }

        if(indexNameBlocks.length == count) {
            forEachIndexNameBlocks(
                    (block, index) -> indexNameBlocks[index].update(index, indexNames[index])
            );
            return;
        }

        IndexNameBlock[] oldIndexNameBlocks = indexNameBlocks;
        int oldCount = oldIndexNameBlocks.length;
        indexNameBlocks = new IndexNameBlock[count];
        updateViewHeightWidth();
        updateIndexNameGap();
        forEachIndexNameBlocks(
                (block, index) -> {
                    indexNameBlocks[index] = index < oldCount ? oldIndexNameBlocks[index] : new IndexNameBlock();
                    indexNameBlocks[index].update(
                            index,
                            indexNames[index],
                            viewWidth,
                            viewHeight,
                            indexNameHeight,
                            indexNameAscent,
                            indexNameGap,
                            showPaddingStart,
                            showPaddingTop,
                            showPaddingBottom
                    );
                }
        );
    }

    @Override
    public void updateIndexName(int index, @NonNull String name) {
        selIndexNameBlock.update(
                index == NOT_INIT ? getLastIndexIndex(name) : index,
                name
        );
        invalidate();
    }

    private int getLastIndexIndex(@NonNull String name) {
        for(int i=0; i<indexNameBlocks.length; i++) {
            if(TextUtils.equals(name, indexNameBlocks[i].indexName)) {
                return i;
            }
        }
        return NOT_INIT;
    }

    @NonNull
    @Override
    public String[] getIndexNames() {
        String[] indexNames = new String[indexNameBlocks.length];
        forEachIndexNameBlocks((block, i) -> indexNames[i] = block.indexName);
        return indexNames;
    }

    private void forEachIndexNameBlocks(@NonNull Carry<IndexNameBlock> consumer) {
        for(IndexNameBlock block : indexNameBlocks) {
            consumer.carry(block);
        }
    }

    private void forEachIndexNameBlocks(@NonNull BiCarry<IndexNameBlock, Integer> consumer) {
        for (int i = 0; i < indexNameBlocks.length; i++) {
            consumer.carry(indexNameBlocks[i], i);
        }
    }

    private static class IndexNameBlock {
        int index;
        String indexName;
        int drawX, drawY;
        final Rect touchRect;
        int centerY;
        boolean inView = true;

        IndexNameBlock() {
            touchRect = new Rect();
        }

        boolean inTouchRect(int x, int y) {
            return inView && touchRect.contains(x, y);
        }

        void update(int i, @NonNull String name) {
            index = i;
            indexName = name;
        }

        void update(int viewWidth, int viewHeight,
                    int textHeight, int textAscent,
                    int gap, int start, int top, int bottom) {
            update(index, indexName,
                    viewWidth, viewHeight,
                    textHeight, textAscent,
                    gap, start, top, bottom);
        }

        void update(int i, @NonNull String name,
                    int viewWidth, int viewHeight,
                    int textHeight, int textAscent,
                    int gap, int start, int top, int bottom) {
            index = i;
            indexName = name;
            drawX = start;

            int halfGap = gap / 2;
            drawY = index * (textHeight + gap) + top + halfGap - textAscent;

            int ascentTop = drawY - textAscent;
            touchRect.set(0, ascentTop - halfGap, viewWidth, ascentTop + textHeight + halfGap);
            centerY = ascentTop + textHeight / 2;

            inView = viewHeight - bottom >= ascentTop + textHeight;
        }

        void draw(@NonNull Canvas canvas, @NonNull Paint paint) {
            if(inView && !TextUtils.isEmpty(indexName)) {
                canvas.drawText(indexName, drawX, drawY, paint);
            }
        }

        void copy(@NonNull IndexNameBlock block) {
            block.index = index;
            block.indexName = indexName;
            block.drawX = drawX;
            block.drawY = drawY;
            block.touchRect.set(touchRect);
            block.centerY = centerY;
            block.inView = inView;
        }

        @NonNull
        @Override
        public String toString() {
            return "IndexNameBlock{" +
                    "index=" + index +
                    ", indexName='" + indexName + '\'' +
                    ", drawX=" + drawX +
                    ", drawY=" + drawY +
                    ", touchRect=" + touchRect +
                    ", centerY=" + centerY +
                    ", inView=" + inView +
                    '}';
        }
    }
}